package com.shenhaofeng.app.download;

import com.shenhaofeng.app.log.AppLog;
import com.trello.rxlifecycle.RxLifecycle;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.concurrent.TimeUnit;

import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import rx.subjects.BehaviorSubject;


/**
 * 下载任务执行器,一个下载任务的实体类,代表一个下载任务,一个取消或者完成的执行器无法再次使用
 * Created by shenhaofeng on 2016/9/14.
 */
public class DownloadExecutor implements Cloneable {


    private static final String TAG = "DownloadExecutor";



    public enum Status {
        DOWNLOADING,
        FREE,
        DOWNLOADED,
        CANCEL
    }



    private enum Event {
        STOP
    }



    private BehaviorSubject<Event> lifecycleSubject = BehaviorSubject.create();


    private Status mStatus = Status.FREE;

    private DownloadRequest mRequest;

    private Config mConfig;

    //下载全局回调
    private Callback mCallback;

    //取消计数器
    private int cancel;


    /**
     * @param downloader
     * @param mRequest
     */
    public DownloadExecutor(Downloader downloader, DownloadRequest mRequest, Callback callback) {
        this.mRequest = mRequest;
        this.mConfig = downloader.getConfig();
        this.mCallback = callback;
    }


    /**
     * 获取任务的下载请求对象
     *
     * @return
     */
    public DownloadRequest getRequest() {
        return mRequest;
    }


    /**
     * 获取执行器的状态
     *
     * @return
     */
    public Status getStatus() {
        return mStatus;
    }


    /**
     * 执行下载任务,需要注意的是,只有在{@link Status}为FREE的状态下该方法才会生效,请调用{@link #isFree()}方法来判断是否应该调用该方法
     *
     * @param listener 下载监听器
     */
    public void execute(DownloadListener listener) {
        if (mStatus != Status.FREE) {
            //只有在空闲时才能够执行
            return;
        }
        mStatus = Status.DOWNLOADING;
        final DownloadParameter downloadParameter = new DownloadParameter();
        Observable.create(new Observable.OnSubscribe<Long>() {
            @Override
            public void call(Subscriber<? super Long> subscriber) {
                try {
                    AppLog.i(TAG, String.format("launch download url:%s", mRequest.url));
                    //代表一个下载任务开始了
                    subscriber.onStart();
                    //解析远程资源,创建本地临时文件
                    URL resourceUrl = new URL(mRequest.url);
                    HttpURLConnection resourceUrlConn = (HttpURLConnection) resourceUrl.openConnection();
                    long count = resourceUrlConn.getHeaderFieldInt("Content-Length", 0);
                    downloadParameter.maxCount = count;
                    AppLog.i(TAG, String.format("remoteResourceSize=%s", count));
                    //创建下载临时文件
                    File mDownloadTempFile = createDownloadTempFile(mConfig, count);
                    downloadParameter.file = mDownloadTempFile;
                    AppLog.i(TAG, String.format("localTempFile=%s", mDownloadTempFile.getAbsoluteFile()));
                    //创建连接对象
                    URL url = new URL(mRequest.url);
                    HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
                    //检查是否已下载了部分数据
                    long length = mDownloadTempFile.length();
                    AppLog.i(TAG, String.format("skip=%s", length));
                    if (length > 0) {
                        //跳过部分数据 RANGE: bytes=2000070-
                        urlConn.setRequestProperty("Range", String.format("bytes=%s-", length));
                    }
                    AppLog.i(TAG, "connecting");
                    //连接远程资源
                    urlConn.connect();
                    AppLog.i(TAG, "connected");
                    if (subscriber.isUnsubscribed()) {
                        subscriber.onCompleted();
                        return;
                    }
                    //获取数据流
                    InputStream remoteResourceInputStream = urlConn.getInputStream();
                    //开始下载数据
                    int len;
                    byte[] bytes = new byte[1024];
                    long totalBytes = length;
                    FileOutputStream fileOutputStream = new FileOutputStream(mDownloadTempFile, true);
                    AppLog.i(TAG, "download start");
                    while (!subscriber.isUnsubscribed() && (len = remoteResourceInputStream.read(bytes)) != -1) {
                        //累计
                        totalBytes += len;
                        fileOutputStream.write(bytes, 0, len);
                        subscriber.onNext(totalBytes);
                    }
                    if (!subscriber.isUnsubscribed()) {
                        subscriber.onCompleted();
                    }
                } catch (IOException e) {
                    subscriber.onError(e);
                }
            }
        }).sample(1, TimeUnit.SECONDS)//每一秒取一次数据
                .compose(RxLifecycle.<Long, Event>bindUntilEvent(lifecycleSubject, Event.STOP))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .subscribe(new DownloadSubscriber(downloadParameter, listener));
    }


    /**
     * 下载执行器停止下载任务
     */
    public void stop() {
        if (mStatus == Status.DOWNLOADING) {
            lifecycleSubject.onNext(Event.STOP);
            lifecycleSubject.onCompleted();
            lifecycleSubject = BehaviorSubject.create();
        }

    }


    /**
     * 下载执行器取消下载任务，下载文件将会被删除
     */
    public void cancel() {
        if (mStatus == Status.DOWNLOADING) {
            cancel++;
            lifecycleSubject.onNext(Event.STOP);
        }
    }


    /**
     * 是否已取消
     *
     * @return
     */
    public boolean isCanceled() {
        return mStatus == Status.CANCEL;
    }


    /**
     * 是否正在下载中
     *
     * @return
     */
    public boolean isDownloading() {
        return mStatus == Status.DOWNLOADING;
    }


    /**
     * 是否下载完成
     *
     * @return
     */
    public boolean isDownloaded() {
        return mStatus == Status.DOWNLOADED;
    }


    /**
     * 是否空闲可执行
     *
     * @return
     */
    public boolean isFree() {
        return mStatus == Status.FREE;
    }


    @Override
    public DownloadExecutor clone() throws CloneNotSupportedException {
        DownloadExecutor downloadExecutor = (DownloadExecutor) super.clone();
        downloadExecutor.mRequest = mRequest.clone();
        downloadExecutor.cancel = 0;
        downloadExecutor.lifecycleSubject = BehaviorSubject.create();
        downloadExecutor.mStatus = Status.FREE;
        return downloadExecutor;
    }

    //====================================================内部方法========================================================//


    /**
     * 创建下载的临时文件
     *
     * @param config
     * @param count
     * @return
     */
    private File createDownloadTempFile(Config config, long count) throws IOException {
        String tempName = createTempFileName(count);
        File tempFile = new File(config.downloadPath(), tempName);
        if (tempFile.exists() && tempFile.isFile()) {
            return tempFile;
        } else {
            boolean result = tempFile.createNewFile();
        }
        return tempFile;
    }


    /**
     * 根据请求创建临时文件的文件名
     *
     * @param count
     * @return
     */
    private String createTempFileName(long count) throws UnsupportedEncodingException {
//        * <p>[链接地址]$[文件名]$[count].downloading.temp</p>
        return String.format("[%s]$[%s]$[%s].downloading.temp", URLEncoder.encode(mRequest.url, "utf8"), mRequest.fileName, count);
    }


    class DownloadParameter {
        public long maxCount;

        public File file;
    }



    class DownloadSubscriber extends Subscriber<Long> {

        private DownloadListener listener;

        private DownloadParameter mDownloadParameter;

        private long lastCount;


        public DownloadSubscriber(DownloadParameter downloadParameter, DownloadListener listener) {
            this.mDownloadParameter = downloadParameter;
            this.listener = listener;
        }


        @Override
        public void onStart() {
            listener.onDownloadStart(DownloadExecutor.this);
            mCallback.onDownloadStart(DownloadExecutor.this);
        }


        @Override
        public void onCompleted() {
            if (mDownloadParameter.maxCount != lastCount) {
                if (cancel == 0) {
                    AppLog.i(TAG, "download stop");
                    mStatus = Status.FREE;
                    listener.onDownloadStop(DownloadExecutor.this);
                    mCallback.onDownloadStop(DownloadExecutor.this);
                } else {
                    AppLog.i(TAG, "download cancel");
                    mStatus = Status.CANCEL;
                    removeTempFile();
                    listener.onDownloadCancel(DownloadExecutor.this);
                    mCallback.onDownloadCancel(DownloadExecutor.this);
                }
            } else {
                AppLog.i(TAG, "download success");
                mStatus = Status.DOWNLOADED;
                DownloadResult downloadResult = new DownloadResult(mRequest.url, mDownloadParameter.file, mDownloadParameter.maxCount);
                listener.onDownloadFinish(downloadResult);
                mCallback.onDownloadSuccess(downloadResult);
            }
        }


        @Override
        public void onError(Throwable e) {
            AppLog.e(TAG, "download error", e);
            listener.onDownloadFailure(DownloadExecutor.this, e);
            mCallback.onDownloadFail(DownloadExecutor.this, e);
        }


        @Override
        public void onNext(Long l) {
            lastCount = l;
            listener.onDownloadProgress(DownloadExecutor.this, mDownloadParameter.maxCount, l);
            mCallback.onDownloadProgress(DownloadExecutor.this, mDownloadParameter.maxCount, l);
            if (mDownloadParameter.maxCount == l) {
                File newFile = new File(mConfig.downloadPath(), mRequest.fileName);
                if (newFile.exists() && newFile.isFile()) {
                    newFile = renameFile(1);
                }
                boolean rename = mDownloadParameter.file.renameTo(newFile);
                if (rename) {
                    mDownloadParameter.file = newFile;
                    AppLog.i(TAG, String.format("file rename %s", mDownloadParameter.file.getName()));
                }
            }

        }


        private File renameFile(int index) {
            File newFile;
            if (mRequest.fileName.contains(".")) {
                int insertIndex = mRequest.fileName.lastIndexOf(".");
                String fileName = String.format("%s(%s)%s"
                        , mRequest.fileName.substring(0, insertIndex)
                        , index
                        , mRequest.fileName.substring(insertIndex, mRequest.fileName.length()));
                newFile = new File(mConfig.downloadPath(), fileName);
            } else {
                newFile = new File(mConfig.downloadPath(), String.format("%s(%s)", mRequest.fileName, index));
            }
            if (newFile.exists() && newFile.isFile()) {
                return renameFile(index + 1);
            } else {
                return newFile;
            }

        }


        /**
         * 删除临时文件
         */
        private void removeTempFile() {
            mDownloadParameter.file.delete();
        }
    }

}
